import services

__all__ = ["CaseChanger", "CustomNullType", "isUseridAuthorized", 
           "convertIntToCash", "convertCashStringToInt"]

class CaseChanger(object):
    """
    This is a common object which should be inherited by other objects. This
    enables the functions of the object to be called with a lower case character
    as the first character instead of an upper case; e.g. race.RegisterTeam()
    can be called as race.registerTeam()
    """

    class CommandLinker(object):
        """
        Because I changed the style of function calling, to keep backwards
        compatibility I shall ensure that I keep the ability to go from XxxxXxx
        to xxxxXxxx (with regards to case). This will be done be using this
        class as a dynamic function with the __call__ passing the self parameter
        to the class method alongside any args and keywords
        """
        def __init__(self, callingClass, functionName, objectAddress):
            """
            Default deconstructor, ensure that the variables are assigned.

            @param str functionName The name of the function to call
            """
            self.functionName  = functionName
            self.objectAddress = objectAddress
            self.callingClass  = callingClass

        def __call__(self, *args, **kw):
            """
            Executed when the class object is used as a function. Get the
            function address and pass the object address as the first parameter
            followed by any additional arguments and keywords.
            """
            vars(self.callingClass)[self.functionName](self.objectAddress,
                                                      *args,
                                                      **kw)

    def __getattr__(self, key):
        """
        Executed automatically when an unkown object is called, this will be
        the magic behind this object. Because it was an unkown function, we will
        attempt to change the case of the first character and see if that exists
        as a function in the class. If so, return the address of that function.

        @param string key The name of the function we wish to test for validity
        @return CommandLinker An object which simulates a function
        """
        originalKey = key
        if key[0].isupper():
            key = key[0].lower() + key[1:]
            if key in vars(self.__class__):
                return self.CommandLinker(self.__class__, key, self)
        raise AttributeError("'%s' object has no attribute '%s'" % (
                                                        self.__class__.__name__,
                                                        originalKey))

    def __hasattr__(self, key):
        """
        Executed automatically when we test if an attribute exists within this
        object. We need to test both upper and lower case characters so that
        we can test if the calling attribute exists as a new style function.

        @param string key The name of the function we wish to test for validity
        @return bool Whether or not the attribute exists.
        """
        originalKey = key
        if key[0].isupper():
            key = key[0].lower() + key[1:]
            if key in vars(self.__class__):
                return True
        return object.__hasattr__(self, originalKey)

class CustomNullType(object):
    """
    This object allows us to return a value from the Command object which
    will enable us to ensure all functions have a return type. This object
    should be able to be cast into most major types all defaulting to None
    equivilent. This ensures that casting the return type is allowed and
    shouldn't fail. This object is here for the sole purpose of being returned
    when a race attempts to access a player who isn't on the server. This is for
    mainly backwards compatability as we did some nasty hooking in the
    predecessor to this version to change the dynamics of event player_spawn.
    As I have now chosen to abandon that nasty hooking, I am choosing the lesser
    of two evils to keep backwards compatability by creating this object to
    return custom None instance and to not error when an unknown attribute is
    called. This will prevent exceptions from occuring. This isn't the best
    practice; but it's certainly better than hooking the event call stack and
    modifying event data which has the potential to break other plugins.
    """
    def __call__(self, *args, **kw):
        """
        Executed when an instance is executed as a function call.
        Simply return the singelton to the CustomNullType object.

        @return CustomNullType A singleton object.
        """
        return self

    def __setitem__(self, key, value):
        """
        Executed when this object has been assigned an item in the form of
        dictionary items. To support command reference, just allow this but
        do nothing.

        @param string key The name of the key
        @param object value The value of the key
        """
        pass

    def __getitem__(self, key):
        """
        Executed when this object has been referenced by item reference. Return
        a refernce to this instance (A custom null type).

        @param string key The name of the key to retrieve
        @return CustomNullType A reference to this object
        """
        return self
    
    def __delitem__(self, key):
        """
        Executed when a del command has been executed on an item object from 
        this instance. Ensure that we cover up the errors by passing quitely.
        """
        pass

    def __getattr__(self, attribute):
        """
        Executed when an unknown attribute has been called. Assume that we
        either want a function address or an object, so return a reference to
        this singleton to handle the null responses.

        @param string key The name of the attribute to retrieve
        @return CustomNullType A reference to this object
        """
        return self
    
    def __contains__(self, attribute):
        """
        Executed when we wish to test for containment within this object. Return
        False as nothing should be contained within this object.
        
        @param str attribute The name of the attribute to test for containment
        """
        return False

    def __bool__(self):
        """
        Executed when this object is cast to a bool.

        @return bool The boolean equivilent of NoneType (False)
        """
        return False

    def __int__(self):
        """
        Executed when this object is cast to an int.

        @return int The integral equivilent of NoneType (0)
        """
        return 0

    def __str__(self):
        """
        Executed when this object is cast to a string.

        @return str The string equivilent of NoneType (empty string)
        """
        return ""

    def __float__(self):
        """
        Executed when this object is cast to a float.

        @return float The float equivilent of NoneType (0.0)
        """
        return 0.0

    def __nonzero__(self):
        """
        Executed when this object attempts to be checked for a zero integral
        reference.

        @return bool False - This object is equivilent to a zero
        """
        return False

    def __coerce__(self, other):
        """
        Executed when this object will be compared with another data type.
        Ensure that we cast the object to the other type.

        @param object other The other object which is being compared with
        @return tuple A tuple containng the value of this object, and the
                      value of the other object in that order.
        """
        if isinstance(other, str):
            return (self.__str__, other)

        if isinstance(other, int):
            return (0, other)

        if isinstance(other, float):
            return (0.0, other)

        if isinstance(other, bool):
            return (False, other)

        return None
    
    def __mul__(self, other):
        """
        Executed when this object is multiplied with another object. Return 0
        since we can assume this object is being represented as an int.
        
        @param object other The other object which is being multiplied with
        @return int/float The value of 0 multiplied with the other object
        """
        return 0.0 if isinstance(other, float) else 0
        
    def __pow__(self, other):
        """
        Executed when this objoect is raised to the power of another value.
        Return an integral value to represent null.
        
        @param object other The other object which is being raise to the power
        @return int/float The value of 0 raised exponentially with the other
        """
        return 0.0 if isinstance(other, float) else 0
    
    def __rshift__(self, other):
        """
        Executed when the right shift operator (>>) is used on this object. We
        cannot do any arbitary bit assignment to null, so return 0.
        
        @param object other The amount of bits to shift to the right 
        @return int The value of 0 shifted to the right by x bits
        """
        return 0
    
    def __lshift__(self, other):
        """
        Executed when the left shift operator (<<) is used on this object. We
        cannot do any arbitary bit assignment to null, so return 0.
        
        @param object other The amount of bits to shift to the left
        @return int The value of 0 shifted to the left by x bits
        """
        return 0
    
    def __not__(self):
        """
        Executed when the bitwise operator 'not' is used on this object. Return
        True since this object represents null in all cases.
        
        @return bool The opposite to this object
        """
        return True
    
    def __abs__(self):
        """
        Returns the absolute value (i.e the positive number) of this object.
        Because this object is none, return 0 which is the absolute value of 0.
        
        @return int The absolute value of this object
        """
        return 0
    
    def __add__(self, other):
        """
        Adds this object to the other value. Because this object is nothing in
        all instances, the value returned will simply be the other object as
        this object will not change that.
        
        @param mixed other The other object being added
        @retrun mixed The other object
        """
        return other
    
    def __neg__(self):
        """
        Returns this object as a negative number.
        
        @return int self This object as a negative number
        """
        return -0.0
    
    def __sub__(self, other):
        """
        Executed when a number is subtracted from this object, simply return
        0 minus the value subtracted.
        
        @param int|float other The amount to subtract
        @return int|float The value deducted from 0
        """
        return 0 - other
    
    
def isUseridAuthorized(userid, capability):
    """
    Tests to see if a userid is authorized to execute a paricular capability.
    This function enables us to test to see if authorization is enabled first;
    this means if no authorization service is loaded it won't error.

    @param int userid The ID of the user to test for authorization capability
    @param str capability The authorization capability to test for auth
    """
    if not services.isRegistered("auth"):
        return False
    return services.use("auth").isUseridAuthorized(userid, capability)

def convertIntToCash(amount, group=3, splitter=",", symbol="$"):
    """
    Converts a numerical value to a specific cash value string. This means that
    the numerical value gets converted into a value which simulates a cash
    string, such as 16000 => $16,000. This also groups the digits if specified.
    
    @param int group The amount of digits to group by
    @param str splitter The string used as a splitter to split the digit groups
    @param str symbol The symbol to use as the cash value
    @return str The cash string
    """
    cash = ""
    amount = str(amount)
    if isinstance(group, int) and group > 0:
        amount = amount[::-1]
        while amount:
            cash += amount[:group] + splitter
            amount = amount[group:]
        cash = cash[:-1]
    else:
        cash += amount[::-1]
    if symbol:
        cash += symbol
    return cash[::-1]

def convertCashStringToInt(cash):
    """
    Converts a string cash value back into a numerical value; i.e, converts
    $16,000 to 16000.
    
    @param str cash The cash string
    @return int The amount of money as an integral value.
    """
    return int(filter(str.isdigit, cash))